﻿using System.Reflection;
using Core.Constants;
using Core.Persistence;
using FluentMigrator.Runner;
using FrameworkCore.Migrations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Core.Migrations;

public static class MigrationExecutor
{
    /// <summary>
    /// 从 dll 路径列表加载程序集并执行迁移（会扫描这些程序集下标注 [Migration] 的类）。
    /// </summary>
    /// <param name="dllPaths">插件 dll 路径集合</param>
    /// <param name="connectionString">数据库连接字符串</param>
    /// <param name="databaseOptions">数据库类型</param>
    /// <param name="dataProvider">用于船舰数据库</param>
    /// <param name="loggerFactory">可选日志工厂（若为 null 则会用控制台）</param>
    public static void ExecuteMigrationsFromDlls(
        IEnumerable<string> dllPaths,
        string connectionString,
        DatabaseOptions databaseOptions,
        IBoilerplateDataProvider dataProvider,
        ILoggerFactory? loggerFactory = null)
    {
        // 1. 加载程序集（去重）
        var assemblies = dllPaths
            .Where(path => !string.IsNullOrWhiteSpace(path))
            .Select(path =>
            {
                try
                {
                    return Assembly.LoadFrom(path);
                }
                catch (Exception ex)
                {
                    // 加载失败记录并返回 null
                    (loggerFactory ?? LoggerFactory.Create(b => b.AddConsole()))
                        .CreateLogger("PluginMigrationExecutor")
                        .LogWarning(ex, "加载程序集失败：{Path}", path);
                    return null;
                }
            })
            .Where(a => a != null)
            .Select(a => a!)
            .Distinct(new AssemblyNameComparer())
            .ToList();

        if (!assemblies.Any())
        {
            (loggerFactory ?? LoggerFactory.Create(b => b.AddConsole()))
                .CreateLogger("PluginMigrationExecutor")
                .LogWarning("没有发现可用的迁移程序集，跳过迁移。");
            return;
        }

        // 2. 准备 DI 容器并注册 FluentMigrator（扫描上述程序集）
        var services = new ServiceCollection();

        // 日志
        if (loggerFactory != null)
            services.AddSingleton(loggerFactory);
        else
            services.AddLogging(lb => lb.AddConsole());

        services.AddFluentMigratorCore()
            .ConfigureRunner(rb =>
            {
                switch (databaseOptions.Provider)
                {
                    case DbProviderKeys.SqlServer:
                        rb.AddSqlServer().WithGlobalConnectionString(connectionString);
                        break;
                    case DbProviderKeys.Mysql:
                        rb.AddMySql8().WithGlobalConnectionString(connectionString);
                        break;
                    case DbProviderKeys.Npgsql:
                        rb.AddPostgres().WithGlobalConnectionString(connectionString);
                        break;
                    case DbProviderKeys.SqLite:
                        rb.AddSQLite().WithGlobalConnectionString(connectionString);
                        break;
                    default:
                        throw new Exception("不支持的数据库类型");
                }

                foreach (var asm in assemblies)
                {
                    rb.ScanIn(asm).For.Migrations();
                }
            })
            .AddLogging(lb => lb.AddFluentMigratorConsole());

        // 3. Build provider 并执行迁移
        using var provider = services.BuildServiceProvider();
        var logger = provider.GetRequiredService<ILoggerFactory>().CreateLogger("PluginMigrationExecutor");

        using var scope = provider.CreateScope();
        dataProvider.CreateDatabaseIfNotExists(connectionString);
        var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();

        try
        {
            logger.LogInformation("开始对 {Count} 个程序集执行迁移...", assemblies.Count);
            runner.MigrateUp(); // 执行所有未执行的迁移
            logger.LogInformation("迁移完成。");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "迁移失败: {Message}", ex.Message);
            throw;
        }
    }

    private class AssemblyNameComparer : IEqualityComparer<Assembly>
    {
        public bool Equals(Assembly? x, Assembly? y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (x is null || y is null) return false;
            return string.Equals(x.GetName().FullName, y.GetName().FullName, StringComparison.OrdinalIgnoreCase);
        }

        public int GetHashCode(Assembly obj) => obj.GetName().FullName!.GetHashCode(StringComparison.OrdinalIgnoreCase);
    }
}
